﻿using System;
using System.Collections.Generic;
using System.Text;

using NHibernate;
using NHibernate.Cfg;
using NHibernate.Engine;
using NHibernate.Tool.hbm2ddl;
using System.IO;
using System.Security.Cryptography;
using System.Reflection;
using NHibernate.Criterion;
using SB.Logging;


namespace SB.Database
{
    class NHibernateDatabase : IDatabase
    {
        #region Properties

        private static readonly ILogger log = LoggingFacade.GetLogger(typeof(NHibernateDatabase));

        private bool connected = false;


        private Configuration cfg = null;
        private ISessionFactory factory = null;

        private Assembly assembly = null;
        private string configResourceName = null;

        [ThreadStatic]
        private ISession session = null;

        [ThreadStatic]
        private ITransaction transaction = null;

        #endregion
        
        #region Connection Management

        public NHibernateDatabase(Assembly assembly, string configResourceName)
        {
            this.assembly = assembly;
            this.configResourceName = configResourceName;
            
            OpenConnection();
        }

        public void OpenConnection()
        {
            if (connected != true)
            {
                try
                {
                    cfg = new Configuration().Configure(assembly, configResourceName);
                    cfg.AddAssembly(assembly);

                    factory = cfg.BuildSessionFactory();
                    session = factory.OpenSession();

                    // Ist die Verbindung wirklich offen?
                    //session.CreateSQLQuery("SELECT TOP 1 * FROM Person").List();

                    //log.Info("Successfully connected to Database!");
                    connected = true;
                }
                catch (Exception ex)
                {
                    throw new Exception("Die Verbindung zur Datenbank konnte nicht hergestellt werden.", ex);
                }
            }
        }

        public void CloseConnection() {
            if (connected)
            {
                try
                {
                    session.Flush();

                    if (transaction != null)
                        transaction.Dispose();

                    if (session != null)
                    {
                        session.Dispose();
                    }

                    if (factory != null)
                        factory.Dispose();

                    log.Info("Successfully disconnected from Database!");
                    connected = false;
                }
                catch (Exception ex)
                {
                    throw new Exception("Die Verbindung zur Datenbank konnte nicht ordnungsgemäß geschlossen werden.", ex);
                }
            }
        }

        #endregion
        
        #region Transaction Management

        public void BeginTransaction()
        {
            try
            {
                if (transaction == null)
                    transaction = session.BeginTransaction();
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public void CommitTransaction()
        {
            try
            {
                if (transaction != null)
                {
                    transaction.Commit();
                    transaction = null;
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public void RollbackTransaction()
        {
            try
            {
                if (transaction != null)
                {
                    transaction.Rollback();
                    transaction = null;
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        #endregion

        public void Flush()
        {
            try
            {
                session.Flush();
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        // Entfernt das übergebene Objekt aus dem Hibernate-Cache und lädt es erneut aus der Datenbank.
        public void RefreshObject(object obj)
        {
            try
            {
                session.Refresh(obj);
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public void Save(object obj) {
            try
            {
                session.SaveOrUpdate(obj);
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public void SaveArray(IEnumerable<object> array)
        {
            foreach (object o in array)
            {
                Save(o);
            }
        }

        public IList<T> GetAll<T>()
        {
            try
            {
                ICriteria criteria = session.CreateCriteria(typeof(T));
                return criteria.List<T>();
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public T Get<T>(object id)
        {
            try
            {
                return (T)session.Get<T>(id);
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public IList<T> Find<T>(string hql)
        {
            try
            {
                IQuery q = session.CreateQuery(hql);
                return q.List<T>();
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public IQuery GetNamedQuery(string queryName)
        {
            try
            {
                return session.GetNamedQuery(queryName);
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public ICriteria CreateCriteria(Type type)
        {
            try
            {
                return session.CreateCriteria(type);
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public ICriteria CreateFindMatchingCriteria<T>(T entity, DatabaseFacade.MatchMode matchMode)
        {
            try
            {
                NHibernate.Criterion.MatchMode m = NHibernate.Criterion.MatchMode.Exact;

                if (matchMode == DatabaseFacade.MatchMode.Start)
                    m = NHibernate.Criterion.MatchMode.Start;
                if (matchMode == DatabaseFacade.MatchMode.End)
                    m = NHibernate.Criterion.MatchMode.End;
                if (matchMode == DatabaseFacade.MatchMode.Anywhere)
                    m = NHibernate.Criterion.MatchMode.Anywhere;


                NHibernate.Criterion.Example example = NHibernate.Criterion.Example.Create(entity)
                    .ExcludeZeroes()
                    .EnableLike(m);
                return CreateCriteria(typeof(T)).Add(example);
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public IList<T> FindMatching<T>(T entity, DatabaseFacade.MatchMode matchMode)
        {
            try
            {
                return CreateFindMatchingCriteria<T>(entity, matchMode).List<T>();
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }


        public void Delete(object obj) {
            try
            {
                session.Delete(obj);
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }


        public void EmptyTables()
        {
            try
            {
                GetNamedQuery("empty.tables").ExecuteUpdate();
            }
            catch (Exception ex)
            {
                throw new Exception("Die Datenbank ist im Moment nicht erreichbar. Bitte kontaktieren sie den Administrator.", ex);
            }
        }

        public void CreateTables()
        {
            new SchemaExport(cfg).Create(true, true);
        }
    }
}
